/**
* @module Visitor
*/
tree = require('./treehugger/tree');
scriptbuilder = require('./scriptbuilder');
state = require('./state');

function Visitor(rootIden){
  this.rootIden = rootIden;
  scriptbuilder.setRootIden(rootIden);
};

Visitor.prototype.reset = function() {
  this.state = new state.State();
  this.state.addRemoteVar(this.rootIden);
  scriptbuilder.setState(this.state);
};

Visitor.prototype.newState = function(){
  return this.state;
};

Visitor.prototype.getLocal = function(){
  return this.state.code.local.join("");
};

Visitor.prototype.getRemote = function(){
  return this.state.code.remote.join("");
};

Visitor.prototype.getInit = function(){
  return this.state.code.init.join("");
};

Visitor.prototype.dispatchVisit = function(node, state) {
  // Get a new state object
  state = this.newState();
  node.accept(this, state);
};

Visitor.prototype.visit = function(node, args) {
  var state = args[1];
};

/**
* List visitor
* @ method visitListNode
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitListNode = function(node, args) {
  var state = args[1];
  
  var v = this;
  var i = 1;
  node.forEach(function(child) {
    child.accept(v, state);
    if( node.parent.cons != "Call" ) 
      scriptbuilder.Local.OptionalSemiColon(state.code.local);
    else {
      if( i < node.length ) {
        state.remote ? state.code.remote.push(', ') : state.code.local.push(', ');
      }
    }
    i++;
  });
};

Visitor.prototype.visitConsBlock = function(node, args) {
  var state = args[1];

  var v = this;
  state.code.local.push(scriptbuilder.Local.BlockStart());
  node[0].forEach(function(child) {
    child.accept(v, state);
    scriptbuilder.Local.OptionalSemiColon(state.code.local);
  });
  state.code.local.push(scriptbuilder.Local.BlockEnd());
}

/**
* FUNCTION CALL constructor visitor
* @ method visitConsCall
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsCall = function(node, args) {
  var state = args[1];

  // function call on remote var
  var self = this;
  
  // Check if left hand side is remote
  var remote = state.traverseIsRemote(node[0]);

  if( remote ) {
    state.remote = true;

    // Evaluate lhs
    node[0].accept(self, state);

    // Opening brace
    state.code.remote.push('(');

    // Evaluate arguments list
    node[1].accept(self, state);

    // Closing brace
    state.code.remote.push(')');

    return;
  }

  state.remote = false;

  // Evaluate lhs
  node[0].accept(this, state);

  // Opening brace
  state.code.local.push('(');

  // Evaluate arguments list
  node[1].accept(this, state);

  // Closing brace
  state.code.local.push(')');

  if( node.parent instanceof tree.ListNode ||
      node.parent instanceof tree.ConsNode && node.parent.cons !== "Call" ) {
      state.code.local.push(';\n');
  }
};

/**
* PROPERTY ACCESS constructor visitor
* @method visitConsPropAccess
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsPropAccess = function(node, args) {
  var state = args[1];
  var lhs = node[0];

  var loopOpts = state.loop[state.loopdepth];

  if( loopOpts && loopOpts.initLoop ) {
    loopOpts.initLoop = false;
    lhs.accept(this, state);
    return;
  }

  // Evaluate left hand side
  lhs.accept(this, state);
  
  // Evaluate right hand side
  if( state.remote) {
    state.code.remote.push('.'+node[1].value);
    return;
  }

  state.code.local.push('.'+node[1].value);
};

/**
* INDEX constructor visitor
* @ method visitConsIndex
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsIndex = function(node, args) {
  var state = args[1];
  throw "Indexes in batches are currently not supported";
};

Visitor.prototype.visitConsNone = function(node, args) {
  return;
}

/**
* VARIABLE constructor visitor
* @ method visitConsVar
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsVar = function(node, args) {
  var state = args[1];

  if( !state.remote && state.isRemote(node[0].value) ) {
    state.remote = true; 

    // This should not happen, but jaba currently uses a datastructure where the local var name is in every subforest.
    // ..not so efficient when transporting large datasets..
    var localVarName;
    if( state.loopdepth > 0 ) localVarName = state.loop[state.loopdepth].item;
    else localVarName = node[0].value;

    state.code.remote.push(scriptbuilder.Remote.OutputVar(node[0].value, state.outputVarNr));
    state.code.local.push(scriptbuilder.Local.OutputVar(localVarName, state.outputVarNr));
    state.outputVarNr++;
    return;
  }
  
  if( state.remote ) {
    state.code.remote.push(scriptbuilder.Remote.Var(node[0].value));
    return;
  }

  var loopOpts = state.loop[state.loopdepth];
  if( loopOpts && loopOpts.inLoop && loopOpts.mode == "both" ) {
    state.code.local.push(scriptbuilder.Local.Var(node[0].value));
    return;
  }
  
  state.code.local.push(scriptbuilder.Local.Var(node[0].value));
};

Visitor.prototype.visitConsVarDecls = function(node, args) {
  var state = args[1];

  var name = node[0][0][0].value;
  var value = node[0][0][1];

  this.state.addRemoteVar(name);

  state.code.remote.push(scriptbuilder.Remote.VarDecl(name));

  state.remote = true;
  value.accept(this, state);

  state.code.remote.push(scriptbuilder.Remote.varDeclEnd());
};

/**
* FOREACH constructor visitor
* @ method visitConsForEach
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsForEach = function(node, args) {
  var state = args[1];

  var callback = node[1][0];
  var name = callback[1][0][0].value;
  var bodyStatements = callback[2];
  
  // Check if left hand side is remote
  if( state.traverseIsRemote(node[0]) ) {
    state.remote = true;
  }
  else {
    state.remote = false;
  }

  state.loop[state.loopdepth+1] = {
    inLoop: true,
    initLoop: true,
    mode: (state.remote ? 'both' : 'local'),
    item: name
  };

  state.loopdepth++;
  if( state.remote ) {

    var root = false;
    if( state.loopdepth - 1 > 0 ) {
      root = state.loop[state.loopdepth-1].item
    }
    
    // For ...
    state.code.remote.push(scriptbuilder.Remote.LoopStart(name));
    state.code.local.push(scriptbuilder.Local.LoopStart(name, root));

    // In ...
    node[0].accept(this, state);

    // Do ...
    state.code.remote.push(scriptbuilder.Remote.LoopBlock());

    bodyStatements.accept(this, state);

    // ... End
    state.code.remote.push(scriptbuilder.Remote.End());
    state.code.local.push(scriptbuilder.Local.End());
  }
  else {
    throw "BatchJS: Local foreach currently not supported";
  }
  state.loopdepth--;

};

/**
* CONDITIONAL constructor visitor
* @ method visitConsIf
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsIf = function(node, args) {
  var state = args[1];    

  var cond = node[0];
  var statements = node[1];
  var elseStatements = node[2];

  var remote = state.traverseIsRemote(cond);
  state.remote = remote;

  if( remote ) {
    state.code.remote.push(scriptbuilder.Remote.IfStart());

    // Evaluate condition
    cond.accept(this, state);

    state.code.remote.push(scriptbuilder.Remote.IfBlock());

    // Evaluate statements
    statements.accept(this, state);

    state.code.remote.push(scriptbuilder.Remote.End());
  }
  else {
    state.code.local.push(scriptbuilder.Local.IfCondStart());

    // Evaluate condition
    cond.accept(this, state);

    state.code.local.push(scriptbuilder.Local.IfCondEnd());

    // Evaluate statements
    statements.accept(this, state);

    if( elseStatements && elseStatements.cons != 'None' ) {
      state.code.local.push(scriptbuilder.Local.Else());
      elseStatements.accept(this, state);
    }
  }
}

/**
* STRING constructor visitor
* @ method visitConsString
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsString = function(node, args) {
  var state = args[1];
  
  state.remote ?
    state.code.remote.push('"' + node[0].value + '"') :
    state.code.local.push('"' + node[0].value + '"');
};

/**
* NUMBER constructor visitor
* @ method visitConsNum
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsNum = function(node, args) {
  var state = args[1];
  
  state.remote ?
    state.code.remote.push(node[0].value) :
    state.code.local.push(node[0].value);
};

/**
* OPERATION constructor visitor
* @ method visitConsOp
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
Visitor.prototype.visitConsOp = function(node, args) {
  var state = args[1];

  var op = node[0];
  var lhs = node[1];
  var rhs = node[2];

  var remote = state.remote;

  state.remote ?
    state.code.remote.push('(') :
    state.code.local.push('(');

  lhs.accept(this, state);

  state.remote = remote;

  state.remote ?
    state.code.remote.push(' ' + op.value + ' ') :
    state.code.local.push(' ' + op.value + ' ');

  state.remote = remote;

  rhs.accept(this, state);

  state.remote = remote;

  state.remote ?
    state.code.remote.push(')') :
    state.code.local.push(')');
};

Visitor.prototype.visitConsPostfixOp = function(node, args) {
  var state = args[1];
  
  state.remote = false;

  var op = node[0];
  var target = node[1];


  target.accept(this, state);
  state.remote ?
    state.code.remote.push(scriptbuilder.Remote.PostfixOp(op.value)) :
    state.code.local.push(scriptbuilder.Local.PostfixOp(op.value));  
}

Visitor.prototype.visitConsAssign = function(node, args) {
  var state = args[1];

  var lhs = node[0];
  var rhs = node[1];

  var remote = state.traverseIsRemote(lhs);

  if( remote ) {
    return;
  }
  else {
    // Lokale assignment
    state.remote = false;
    lhs.accept(this, state);
    state.code.local.push(scriptbuilder.Local.Assignment());
    rhs.accept(this, state);
  }
}

/**
* VALUE visitor
* @ method visitValue
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
 Visitor.prototype.visitValue = function(node, args) {
  var state = args[1];
  
  state.remote ?
    state.code.remote.push(node.value) :
    state.code.local.push(node.value);
};

exports.Visitor = Visitor;